/*
	Copyright (c) 2009 - 2010 conleon1988 (conleon1988@gmail.com)

	Permission is hereby granted, free of charge, to any person
	obtaining a copy of this software and associated documentation
	files (the "Software"), to deal in the Software without
	restriction, including without limitation the rights to use,
	copy, modify, merge, publish, distribute, sublicense, and/or sell
	copies of the Software, and to permit persons to whom the
	Software is furnished to do so, subject to the following
	conditions:

	The above copyright notice and this permission notice shall be
	included in all copies or substantial portions of the Software.

	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
	EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
	OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
	NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
	HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
	WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
	FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
	OTHER DEALINGS IN THE SOFTWARE.
*/

#include "opengenie.h"

//this is the binary representation of an SMD header dump that i made...(the game was : phantasy star)
static const unsigned char ALLIGNEDBY(DEFAULT_MEMORY_ALLIGNMENT) SMD_PAD_SMPD[512] = {
    0xc0,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0xaa,0xbb,0x06,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
};

unsigned char SET_FUNC_ATTR(cold) writeRomHeader(RomInfo* rom ,const char* outFilename)
{
	FILE* f;

	if(!rom)
		return RST_FAILED;

	f = fopen(outFilename,"wb");

	if(!f)
		return RST_FAILED;

	fwrite(rom->romHeader,1,512,f);

	fclose(f);

	return RST_SUCCESS;
}

static unsigned char SET_FUNC_ATTR(returns_twice) SET_FUNC_ATTR(hot) writeSegaCDISO(RomInfo* rom,const char* outFilename)
{
	char b = 0;
	FILE* dst ;
	StackPatchDescription* top ;
	unsigned __int64 addr = 0;

	writeToLog("Writting SEGA CD ISO(THIS CAN BE SLOW) [in=%s][out=%s]\n",rom->filename,outFilename);
	dumpToConsole("Writting SEGA CD ISO(THIS CAN BE SLOW) [in=%s][out=%s]\n",rom->filename,outFilename);

	//Sega CD mode is a bit complex to do because we don't want all the rom in the memory.
	//So we patch addresses while we write the file byte by byte

	if(!rom->scdHandle)
	{
		writeToLogSimple("Sega CD handle is not active! Aborting\n");
		return RST_FAILED;
	}

	fseek(rom->scdHandle,0,SEEK_SET);

	dst = fopen(outFilename,"wb");

	if(!dst)
	{
		dumpToConsole("Unable to write [%s]\n",outFilename);
		writeToLog("Unable to write [%s]\n",outFilename);

		return RST_FAILED;
	}

	//Write any bytes needed for padding

	#define GET_BYTE b = fgetc(rom->scdHandle)
	#define PUT_BYTE fputc(b,dst)

	while(rom->scdJumpLevel>0)
	{
		rom->scdJumpLevel--;
		
		GET_BYTE;
		PUT_BYTE;
	}

	//build dump

	//First we get the top element from the sorted stack patch list.No loops are involved!
	top = getPatchDescriptionStackTop();

	addr = 0;
	
	while(!feof(rom->scdHandle))
	{
		addr = (unsigned __int64)ftell(rom->scdHandle);

	 	//write our code!
		if(top)
		{
			if(top->patchAddress == addr)
			{
				writeToLog("Patching SCD rom Address %03X , with [%s : %03X] \n",addr,top->patchData,top->patchDataLength);

				//write patch
				if(fwrite(top->patchData,1,top->patchDataLength,dst) != top->patchDataLength)
					writeToLog("Warning : Unable to write all required bytes to SCD ISO! Disk full ? [dst = %s]\n",outFilename);

				//move stream
				fseek(rom->scdHandle,top->patchDataLength,SEEK_CUR);

				//next
				top = getPatchDescriptionStackTop();
			}
			else//usual task
			{
				GET_BYTE;
				PUT_BYTE;
			}
		}
		else//usual task 
		{	//don't use buffer...just write the file byte by byte!
			GET_BYTE;
			PUT_BYTE;
		}
	}

	#undef PUT_BYTE
	#undef GET_BYTE

	fclose(dst);
	fclose(rom->scdHandle);
	rom->scdHandle = NULL;//important

	releaseStackPatchList();
	
	return RST_SUCCESS;
}

unsigned char SET_FUNC_ATTR(returns_twice) SET_FUNC_ATTR(hot) writeRom(RomInfo* rom,const char* outFilename,unsigned char forceMode)
{
	int blockIndex=0;
	int blockOffs=0;
	int blockRealOffsMid=0;
	REG int blockRealOffs=0;
	REG int jmp_shl_blk = 0;
	REG int jmp_shl_blk_adi1 = 0;
	unsigned char* result;

	FILE* f;

	unsigned char writeMode;

	if(!rom)
		return RST_FAILED;

	if(rom->mode == FMT_SCD)
		return writeSegaCDISO(rom,outFilename);

	writeToLog("Writting rom [in=%s][out=%s]\n",rom->filename,outFilename);

	if(forceMode == FMT_UNKNOWN)
		writeMode = getFileType(outFilename);
	else
		writeMode = forceMode;

	switch(writeMode)
	{
		case FMT_GEN:
		case FMT_BIN:
		case FMT_32X:
		{
			f = fopen(outFilename,"wb");

			if(!f)
			{
				dumpToConsole("Unable to write [%s]\n",outFilename);
				writeToLog("Unable to write [%s]\n",outFilename);

				return RST_FAILED;
			}

			//rom is already decoded , just write the decoded data
			fwrite(rom->romData,1,(unsigned int)rom->romLength,f);

			fclose(f);
		}
		break;

		case FMT_SMD:
		{
			result = xalloc((unsigned int)rom->romLength);

			if(!result)
			{
				dumpToConsole("Unable to write [%s]. SMD RESULT BLOCK  CANNOT BE ALLOCATED. OUT OF MEMORY?? \n",outFilename);
				writeToLog("Unable to write [%s]. SMD RESULT BLOCK   CANNOT BE ALLOCATED. OUT OF MEMORY?? \n",outFilename);

				return RST_FAILED;
			}

			for(blockIndex = 0; blockIndex < ((int)rom->romLength / 16384); blockIndex++)
			{
				blockRealOffs = blockIndex * 16384;
				blockRealOffsMid = blockRealOffs + 8192;

				for(blockOffs = 0; blockOffs < 8192 /* 16384 / 2 */; blockOffs++)
				{
					jmp_shl_blk =  ( blockOffs << 1 );
					jmp_shl_blk_adi1 = blockRealOffs + (jmp_shl_blk);
			
					*(result + (blockRealOffsMid+ blockOffs)) =  *(rom->romData + jmp_shl_blk_adi1);
					*(result + (blockRealOffs + blockOffs)) =  *(rom->romData + (jmp_shl_blk_adi1 + 1) );
				}
			}
/*Replaced the smd-byte padding-dump file with bin2c object/table from that dump
			FILE* pad = fopen("SMD_PAD.smdp","rb");

			if(!pad)
			{
				dumpToConsole("Unable to write [%s]. SMD_PAD.smdp MISSING !\n",outFilename);
				writeToLog("Unable to write [%s]. SMD_PAD.smdp MISSING !\n",outFilename);

				return RST_FAILED;
			}
*/
			f = fopen(outFilename,"wb");

			if(!f)
			{
				dumpToConsole("Unable to write [%s]\n",outFilename);
				writeToLog("Unable to write [%s]\n",outFilename);

				free(result);

				return RST_FAILED;
			}

			//fread(rom->romHeader,1,512,pad);
			//fclose(pad);

			//write header padded bytes
			fwrite(SMD_PAD_SMPD/*rom->romHeader*/,1,512,f);

			//write interleaved data
			fwrite(result,1,(unsigned int)rom->romLength,f);

			fclose(f);
			free(result);
		}
		break;

		case FMT_MD://multigame doctor format
		default:
		{
			dumpToConsole("Unsupported format.Unable to write :( \n");
			writeToLogSimple("Unsupported format.Unable to write :( \n");
		}
		break;
	}

	

	return RST_SUCCESS;
}


